package com.android.camera2.host.mode;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.ImageFormat;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.media.MediaRecorder;
import android.os.SystemClock;
import android.util.Log;
import android.util.Size;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.widget.Chronometer;
import androidx.annotation.NonNull;
import com.android.camera2.common.app.IApp;
import com.android.camera2.common.mode.ICameraMode;
import com.android.camera2.common.ui.IAppUi;
import com.android.camera2.common.utils.CameraParameter;
import com.android.camera2.common.utils.CameraUtils;
import com.android.camera2.common.utils.LogUtil;
import java.io.IOException;
import java.util.Arrays;

import static com.android.camera2.common.utils.CameraParameter.FRONT_CAMERA;
import static com.android.camera2.common.utils.CameraParameter.MAIN_CAMERA;

public class videoModule implements ICameraMode {
    private static final String TAG = videoModule.class.getSimpleName();

    private Chronometer chronometer;
    //Camera
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private static IApp mApp;
    private Context mContext;

    //Surface
    private Surface mPreviewSurface;
    private CaptureRequest.Builder mPreviewBuilder;
    private Size size;
    private ImageReader mCaptureReader;
    private CameraCaptureSession mCaptureSession;

    //camera id
    private static String CURRENT_CAMERA = "0";
    private boolean isrecording = false;
    private MediaRecorder mMediaRecorder;

    public videoModule(IApp app) {
        mApp = app;
        mContext = app.getActivity();
    }

    @Override
    public void init() {
        LogUtil.i(TAG, "init");
        chronometer = mApp.getCameraAppUi().chronometer;
        mApp.getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mApp.getAppUi().setPrevSurfaceListener(previewSurfaceCallback);
                mApp.getAppUi().setShutterListener(mRecordListener);
                mApp.getAppUi().setCameraSwitcherListener(cameraSwitchListener);
            }
        });

        getCameraId();
        openCamera();
        size = findBestPreviewSize(CameraParameter.RATIO_DEFAULT);
        mApp.getAppUi().setPreviewSize(size);
    }

    private void getCameraId() {
        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
        try {
            String[] cameraIds = mCameraManager.getCameraIdList();
            for (String id : cameraIds) {
                Log.e(TAG, "getCameraId: " + id);
            }
            for (String cameraid : cameraIds) {
                CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(cameraid);
                //skip the front camera device
                Integer facing = characteristics.get(CameraCharacteristics.LENS_FACING);
                if (facing != null && facing == CameraCharacteristics.LENS_FACING_FRONT) {
                    continue;
                }
                StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                if (map == null) {
                    continue;
                }
                CURRENT_CAMERA = cameraid;
                break;
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void resume() {
        LogUtil.i(TAG, "resume");
        openCamera();
    }

    @Override
    public void pause() {
        LogUtil.i(TAG, "pause");
        if (isrecording) {
            stopRecordingVideo();
        }
//        stopPreview();
    }

    @Override
    public void destroy() {
        LogUtil.i(TAG, "destroy");
//        mApp.getAppUi().setPrevSurfaceListener(null);
        mApp.getAppUi().setShutterListener(null);
        mApp.getAppUi().setCameraSwitcherListener(null);
        closeCamera();
        mApp.releaseBackgroundHandler();
    }

    @SuppressLint("MissingPermission")
    @Override
    public void openCamera() {
        try {
            mCameraManager.openCamera(CURRENT_CAMERA, cameraDeviceCallback, mApp.getBackgroundHandler());
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }


    private CameraDevice.StateCallback cameraDeviceCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            LogUtil.i(TAG, "camera device opened ..");
            mCameraDevice = camera;
            startPreview();
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            LogUtil.i(TAG, "camera device open onDisconnected --cameraDevice =" + camera);
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            LogUtil.i(TAG, "camera device open onError");
        }
    };

    //SurfaceHolder Callback
    private SurfaceHolder.Callback previewSurfaceCallback = new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(@NonNull SurfaceHolder holder) {
            LogUtil.i(TAG, "surfaceCreated = " + holder.getSurface());
            mPreviewSurface = holder.getSurface();
            startPreview();
        }

        @Override
        public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {
            LogUtil.i(TAG, "surfaceChanged = " + holder.getSurface());
            mPreviewSurface = holder.getSurface();
        }

        @Override
        public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
            LogUtil.i(TAG, "onSurfaceDestroyed");
            mPreviewSurface = null;
        }
    };

    @Override
    public void startPreview() {
        LogUtil.i(TAG, "mPreviewSurface = " + mPreviewSurface + "\nmPreviewBuilder = " + mPreviewBuilder + "\nmCameraDevice = " + mCameraDevice);
        if (mPreviewSurface == null) {
            LogUtil.i(TAG, "surface is null ...");
            return;
        }

//        Toast.makeText(mContext,"video mode is opened",Toast.LENGTH_SHORT).show();
        try {
            mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mPreviewBuilder.addTarget(mPreviewSurface);

            mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, mCaptureReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    LogUtil.e(TAG, "onConfigured______success");
                    try {
                        mCaptureSession = session;
                        session.setRepeatingRequest(mPreviewBuilder.build(), mPreviewCallback, mApp.getBackgroundHandler());
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {

                }
            }, mApp.getBackgroundHandler());
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    private CameraCaptureSession.CaptureCallback mPreviewCallback = new CameraCaptureSession.CaptureCallback() {
        @Override
        public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) {
            super.onCaptureProgressed(session, request, partialResult);
        }

        @Override
        public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
            super.onCaptureCompleted(session, request, result);
            LogUtil.i(TAG, "onCaptureCompleted");
        }
    };

    CameraCharacteristics characteristics = null;

    private Size findBestPreviewSize(float ratio) {
        //configure surfaces

        int mPreviewWidth = 0;
        int mPreviewHeight = 0;
        try {
            characteristics = mCameraManager.getCameraCharacteristics(CURRENT_CAMERA);

        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        if (characteristics != null) {
            StreamConfigurationMap streamMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] supportSizes = streamMap.getOutputSizes(ImageFormat.JPEG);

//            mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            float tolerant = 0.05f;
            int fullScreenWidth = mContext.getResources().getDisplayMetrics().widthPixels;
            int fullScreenHeight = mContext.getResources().getDisplayMetrics().heightPixels;
            LogUtil.i(TAG, "findBestPreviewSize : fullScreenWidth = " + fullScreenWidth + " fullScreenHeight = " + fullScreenHeight);
            Size bestCaptureSize = new Size((int) (fullScreenWidth * ratio), fullScreenWidth);
            float bestRatio = ratio;
            for (Size size : supportSizes) {
                if (Math.abs((float) size.getWidth() / size.getHeight() - ratio) < tolerant) {
                    tolerant = Math.abs((float) size.getWidth() / size.getHeight() - ratio);
                    bestCaptureSize = size;
                    bestRatio = (float) size.getWidth() / size.getHeight();
                }
            }
            LogUtil.i(TAG, "bestCaptureSize = " + bestCaptureSize);
            //capture surface
            int mCaptureWidth = bestCaptureSize.getWidth();
            int mCaptureHeight = bestCaptureSize.getHeight();
            //init imageReader
            mCaptureReader = ImageReader.newInstance(mCaptureWidth, mCaptureHeight, ImageFormat.JPEG, 1);
            mCaptureReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
                @Override
                public void onImageAvailable(ImageReader reader) {
                    LogUtil.i(TAG, "reader = " + reader);
                }
            }, mApp.getBackgroundHandler());

            //preview surface
            int previewHeight = fullScreenWidth - fullScreenWidth % (16 * 3);
            int previewWidth = (int) (previewHeight * ratio);
            if (previewWidth != mPreviewWidth || previewHeight != mPreviewHeight) {
                mPreviewHeight = previewHeight;
                mPreviewWidth = previewWidth;
            }
            if (ratio == CameraParameter.RATIO_full) {
                mPreviewWidth = fullScreenHeight;
                mPreviewHeight = fullScreenWidth;
            }

        }

        return new Size(mPreviewWidth, mPreviewHeight);
    }

    @Override
    public void stopPreview() {
        LogUtil.i(TAG, "stopPreview");
        if (mCaptureSession != null) {
            try {
                mCaptureSession.abortCaptures();
                mCaptureSession.close();
//                mCaptureSession = null;
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void closeCamera() {
        LogUtil.i(TAG, "closeCamera");
        if (null != mCaptureSession) {
            mCaptureSession.close();
            mCaptureSession = null;
        }
    }

    private IAppUi.ShutterListener mRecordListener = new IAppUi.ShutterListener() {
        @Override
        public void onShutterLongPressed() {
            LogUtil.i(TAG, "onShutterLongPressed");
        }

        @Override
        public void onShutterPressed() {

            if (null == mCameraDevice || null == mPreviewSurface) {
                return;
            }
            if (isrecording) {
                //stopRecording
                stopRecordingVideo();
                startPreview();

            } else {
                try {
                    //startRecording
                    mApp.getSoundManager().playRecordingSoundStart();
                    chronometer.setBase(SystemClock.elapsedRealtime());
                    chronometer.start();
                    stopPreview();
                    setUpMediaRecorder();
                    mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD);

                    // Set up Surface for the MediaRecorder
                    Surface recorderSurface = mMediaRecorder.getSurface();
                    mPreviewBuilder.addTarget(mPreviewSurface);
                    mPreviewBuilder.addTarget(recorderSurface);
                    mCameraDevice.createCaptureSession(Arrays.asList(mPreviewSurface, recorderSurface), recordingStateCallback, null);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                }
            }

        }
    };

    private CameraCaptureSession.StateCallback recordingStateCallback = new CameraCaptureSession.StateCallback() {
        @Override
        public void onConfigured(@NonNull CameraCaptureSession session) {
            mMediaRecorder.start();
            isrecording = true;
            LogUtil.i(TAG, "isRecording now");
            mCaptureSession = session;
            try {
                mCaptureSession.setRepeatingRequest(mPreviewBuilder.build(), null, null);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onConfigureFailed(@NonNull CameraCaptureSession session) {

        }
    };

    private void stopRecordingVideo() {
        mApp.getSoundManager().playRecordingSoundStart();
        chronometer.stop();
        isrecording = false;
        mMediaRecorder.stop();
        mMediaRecorder.reset();

        mApp.getAppUi().displayThumbnail(CameraUtils.getLatestVideoThumbBitmap());
    }

    private int mRecordOrientation = 90;

    private void setUpMediaRecorder() {
        if (mMediaRecorder == null) {
            mMediaRecorder = new MediaRecorder();
        }
        String filepath = CameraUtils.getFilePath(true, System.currentTimeMillis());
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
        LogUtil.i(TAG,"file_path = " + filepath);
        mMediaRecorder.setOutputFile(filepath);
        CameraUtils.insertVideoToDB(filepath);
        mMediaRecorder.setVideoEncodingBitRate(10000000);
        mMediaRecorder.setVideoFrameRate(30);
        mMediaRecorder.setVideoSize(size.getWidth(), size.getHeight());
        mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

        int rotation = mApp.getActivity().getWindowManager().getDefaultDisplay().getRotation();
        LogUtil.i(TAG, "characteristics_ = " + characteristics);
        mRecordOrientation = CameraUtils.getRecordingRotation(rotation, characteristics);
        mMediaRecorder.setOrientationHint(mRecordOrientation);
        try {
            mMediaRecorder.prepare();
        } catch (IOException e) {
            Log.e(TAG, "error prepare video record:" + e.getMessage());
        }
    }

    private IAppUi.CameraSwitchListener cameraSwitchListener = new IAppUi.CameraSwitchListener() {
        @Override
        public void onCameraSwitch() {
            LogUtil.d(TAG, "do onCameraSwitch " + CURRENT_CAMERA);
//            mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);

            if (CURRENT_CAMERA.equals(MAIN_CAMERA)) {
                CURRENT_CAMERA = FRONT_CAMERA;
                mRecordOrientation = 270;
            } else if (CURRENT_CAMERA.equals(FRONT_CAMERA)) {
                CURRENT_CAMERA = MAIN_CAMERA;
                mRecordOrientation = 90;
            }
            try {
                characteristics = mCameraManager.getCameraCharacteristics(CURRENT_CAMERA);
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
            stopPreview();
            closeCamera();
            openCamera();
        }
    };

}
